<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <script src="remarkable.min.js"></script>
        <link rel="stylesheet" type="text/css" href="github-markdown.css">
        <style>
            body, html{
                margin: 0;
                padding: 0;
            }
            #preview{
                overflow: auto;
                color: rgb(0, 0, 0);
                
                min-width: 200px;
                max-width: 790px;
                margin: 0 auto;
                padding: 30px;
                
                -webkit-font-smoothing: antialiased;
                -moz-osx-font-smoothing: grayscale;
            }
            #preview pre code div {
                background: transparent !important;
            }
            #preview .ace_line {
                white-space: pre;
            }
            #preview .ace_static_highlight {
                font-family: inherit;
                font-size: inherit;
            }
        </style>
    </head>
    <body>
        <div class="markdown-body" id="preview">
        
        </div>
        <script>
            (function(){
                /*global Remarkable*/
                var preview = document.getElementById("preview");
                var host = location.href.match(/host=(.*?)(\&|$)/)[1];
                var id = location.href.match(/id=(.*)/)[1];
                var parent = window.opener || window.parent;
                
                var count = 0;
                
                var md = new Remarkable({
                    html:         true,        // Enable HTML tags in source
                    xhtmlOut:     false,        // Use '/' to close single tags (<br />)
                    breaks:       false,        // Convert '\n' in paragraphs into <br>
                    langPrefix:   'language-',  // CSS language prefix for fenced blocks
                    linkify:      true,        // Autoconvert URL-like text to links
                    
                    // Enable some language-neutral replacement + quotes beautification
                    typographer:  true,
                    
                    // Double + single quotes replacement pairs, when typographer enabled,
                    // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German.
                    quotes: '“”‘’',
                    
                    // Highlighter function. Should return escaped HTML,
                    // or '' if the source string is not changed
                    highlight: function (str, lang) { 
                        var id = count++;
                        send({ 
                            message: "highlight", 
                            content: str, 
                            lang: lang, 
                            hid: id 
                        });
                        
                        return '<span id="findme' + id + '"></span>' + str; 
                    }
                });
                md.renderer.rules.paragraph_open = function (tokens, idx) {
                    var line;
                    if (tokens[idx].lines && tokens[idx].level === 0) {
                        line = tokens[idx].lines[0];
                        return '<p class="line" data-line="' + line + '">';
                    }
                    return '<p>';
                };
        
                md.renderer.rules.heading_open = function (tokens, idx) {
                    var line;
                    if (tokens[idx].lines && tokens[idx].level === 0) {
                        line = tokens[idx].lines[0];
                        return '<h' + tokens[idx].hLevel + ' class="line" data-line="' + line + '">';
                    }
                    return '<h' + tokens[idx].hLevel + '>';
                };
                
                md.renderer.rules.list_item_open = function (tokens, idx) {
                    var line;
                    if (tokens[idx].lines) {
                        line = tokens[idx].lines[0];
                        return '<li class="line" data-line="' + line + '">';
                    }
                    return '<li>';
                };
                
                md.renderer.rules.blockquote_open = function (tokens, idx) {
                    var line;
                    if (tokens[idx].lines) {
                        line = tokens[idx].lines[0];
                        return '<blockquote class="line" data-line="' + line + '">';
                    }
                    return '<blockquote>';
                };
                
                // Build offsets for each line (lines can be wrapped)
                // That's a bit dirty to process each line everytime, but ok for demo.
                // Optimizations are required only for big texts.
                var sourceLikeDiv, cStyle;
                function buildScrollMap(value) {
                    var i, offset, nonEmptyList, pos, a, b, lineHeightMap, linesCount,
                            acc, _scrollMap;
                    
                    if (!sourceLikeDiv) {
                        sourceLikeDiv = document.createElement("div");
                        sourceLikeDiv.style.position = 'absolute';
                        sourceLikeDiv.style.visibility = 'hidden';
                        sourceLikeDiv.style.height = 'auto';
                        sourceLikeDiv.style.width = preview.clientWidth;
                        sourceLikeDiv.style.fontSize = cStyle.fontSize;
                        sourceLikeDiv.style.fontFamily = cStyle.fontFamily;
                        // sourceLikeDiv.style.lineHeight = cStyle.lineHeight;
                        sourceLikeDiv.style.whiteSpace = "pre";
                    }
                    document.body.appendChild(sourceLikeDiv);
                    
                    offset = 0;
                    _scrollMap = [];
                    nonEmptyList = [];
                    lineHeightMap = [];
            
                    acc = 0;
                    value.split('\n').forEach(function(str) {
                        var h, lh;
            
                        lineHeightMap.push(acc);
            
                        if (str.length === 0) {
                            acc++;
                            return;
                        }
            
                        sourceLikeDiv.innerHTML = str;
                        var sStyle = getComputedStyle(sourceLikeDiv);
                        h = parseFloat(sStyle.height);
                        lh = parseFloat(sStyle.fontSize);
                        acc += Math.round(h / lh);
                    });
                    sourceLikeDiv.remove();
                    lineHeightMap.push(acc);
                    linesCount = acc;
            
                    for (i = 0; i < linesCount; i++) { _scrollMap.push(-1); }
            
                    nonEmptyList.push(0);
                    _scrollMap[0] = 0;
            
                    var lines = preview.querySelectorAll(".line");
                    for (var i = 0, l = lines.length; i < l; i++) {
                        var t = lines[i].getAttribute("data-line");
                        if (t === '') { return; }
                        t = lineHeightMap[t];
                        if (t !== 0) { nonEmptyList.push(t); }
                        _scrollMap[t] = Math.round(lines[i].offsetTop + offset);
                    }
            
                    nonEmptyList.push(linesCount);
                    _scrollMap[linesCount] = preview.scrollHeight;
            
                    pos = 0;
                    for (i = 1; i < linesCount; i++) {
                        if (_scrollMap[i] !== -1) {
                            pos++;
                            continue;
                        }
            
                        a = nonEmptyList[pos];
                        b = nonEmptyList[pos + 1];
                        _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) 
                            + _scrollMap[a] * (b - i)) / (b - a));
                    }
                    
                    _scrollMap[0] = 0;
                    return _scrollMap;
                }
            
                var ckb;
                function setKeys(list){
                    ckb = {};
                    
                    list.forEach(function(item) {
                        var binding = item.binding;
                        var command = item.command;
                        var hashId  = binding.hashId;
                        var hash    = (ckb[hashId] || (ckb[hashId] = {}));
                        
                        if (!hash[binding.key]) {
                            hash[binding.key] = command;
                        } else {
                            if (!Array.isArray(hash[binding.key]))
                                hash[binding.key] = [hash[binding.key]];
                            
                            hash[binding.key].push(command);
                        }
                    }, this);
                }
                
                var timer;
                function scrollTo(posTo){
                    clearInterval(timer);
                    var curPos = document.body.scrollTop;
                    var dist = posTo - curPos;
                    var incr = dist / 10;
                    
                    timer = setInterval(function(){
                        if (incr < 0
                          ? document.body.scrollTop + incr < posTo
                          : document.body.scrollTop + incr > posTo) {
                            document.body.scrollTop = posTo;
                            clearInterval(timer);
                        }
                        else
                            document.body.scrollTop += incr;
                    }, 10);
                }
                
                document.addEventListener("keydown", function(e){
                    var hashId = 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0)
                        | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
                    
                    var keys = ckb[hashId];
                    var cmd  = keys && keys[e.keyCode];
                    
                    if (cmd) {
                        send({ message: "exec", command: cmd });
                        
                        e.preventDefault();
                        e.stopPropagation();
                    }
                });
                
                var scrollMap, lastValue, highlightStyle;
                window.addEventListener("message", function(e){
                    if (host != "local" && e.origin !== host)
                        return;
                    
                    if (e.data.type == "document") {
                        lastValue = e.data.content;
                        preview.innerHTML = md.render(lastValue);
                        cStyle = e.data;
                        scrollMap = null;
                    }
                    else if (e.data.type == "keys")
                        setKeys(e.data.keys);
                    else if (e.data.type == "scroll") {
                        if (!scrollMap)
                            scrollMap = buildScrollMap(lastValue);
                        var line = Math.max(0, e.data.lineNumber - 2);
                        var posTo = scrollMap[line];
                        
                        scrollTo(posTo);
                    }
                    else if (e.data.type == "highlight") {
                        var el = document.getElementById("findme" + e.data.hid);
                        if (!el) return;
                        if (!highlightStyle) {
                            highlightStyle = document.head.appendChild(document.createElement("style"));
                            highlightStyle.innerText = e.data.css;
                        }
                        el.parentNode.innerHTML = e.data.html;
                    }
                }, false);
                
                window.addEventListener("focus", function(){
                    send({ message: "focus" });
                });
                
                function send(message){
                    message.id = id;
                    parent.postMessage(message, 
                        host == "local" ? "*" : host);
                }
                
                window.start = function(win){
                    parent = win || window.parent;
                    send({ message: "stream.document" });
                }
                
                if (parent != window)
                    window.start(parent);
            })();
        </script>
    </body>
</html>